import 'package:sqliteasy_builder/sql_checker/sql_checker.dart';
import 'package:sqliteasy_core/enums/conflict_strategy.dart';
import 'package:sqliteasy_core/sqliteasy_core.dart' hide ColumnInfo, TableInfo, ForeignKeyInfo, IndexInfo;
import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:source_gen/source_gen.dart';
import '../model/database.dart';
import '../model/dao.dart';
import '../model/code_fragment.dart';
import '../errors.dart';
import '../const.dart';

import 'database_util.dart';
import 'ext.dart';
import 'util.dart';
import '../log.dart';
import 'generators/entity_insertion_adapter_gen.dart';

class DaoGenerator {
  static final conflictStrategyMap = {
    ConflictStrategy.abort: "ABORT",
    ConflictStrategy.ignore: "IGNORE",
    ConflictStrategy.replace: "REPLACE"
  };

  static final _daoChecker = TypeChecker.fromRuntime(Dao);
  static final _insertChecker = TypeChecker.fromRuntime(Insert);
  static final _updateChecker = TypeChecker.fromRuntime(Update);
  static final _deleteChecker = TypeChecker.fromRuntime(Delete);
  static final _queryChecker = TypeChecker.fromRuntime(Query);
  static final _daoActionChecker = TypeChecker.fromRuntime(DaoAction);

  /// 该Element是否是一个DaoClass
  static bool isDaoClass(Element element) {
    return element is ClassElement && _daoChecker.hasAnnotationOf(element);
  }

  DatabaseInfo _databaseInfo;

  final Set<DartType> _valueConverterEntitySet = Set();
  final Map<DartType, String> _valueConverterNameMap = Map();

  DaoGenerator(this._databaseInfo);

  CodeFragment genDaoCode(ClassElement daoElement) {
    List<CodeFragment> daoMethodImplList = daoElement.methods
        .where((element) => element.isAbstract)
        .map((e) => _genDaoMethodImpl(e))
        .toList();

    Set<String> imports = Set();
    for (var value in daoMethodImplList) {
      imports.addAll(value.importList ?? []);
    }
    final daoImplName = DatabaseUtil.getDaoImplName(daoElement);
    return CodeFragment("""
    class ${daoImplName} extends ${daoElement.name} {
      SQLiteasyDatabase ${Const.dbFieldName};
      ${daoImplName}(this.${Const.dbFieldName});
      
      ${daoMethodImplList.map((e) => e.content).join("\n")}
    }
    """, imports.toList());
  }

  List<CodeFragment> genValueConverterCode() {
    return _valueConverterEntitySet.map((e) => _genValueConverterOfEntity(e));
  }

  CodeFragment _genDaoMethodImpl(MethodElement element) {
    if (!element.returnType.isVoid && !element.returnType.isDartAsyncFuture) {
      throw SQLiteasyBuildError("Dao方法的返回值类型必须是void或Future<T>");
    }
    List sqlStatementAndCodeFragment = _generateSqlStatement(element);
    SqlStatement statement = sqlStatementAndCodeFragment[0] as SqlStatement;
    SQLiteChecker().check(statement.statement);
    CodeFragment codeFragment = sqlStatementAndCodeFragment[1] as CodeFragment;
    EntityInfo entityInfo = sqlStatementAndCodeFragment[2] as EntityInfo;
    if (sqlStatementAndCodeFragment == null) {
      return CodeFragment("");
    }
    String parameterList = element.parameters.map((e) => e.toString().replaceAll("*", "")).join(", ");
    DartType retEntityType = DatabaseUtil.getDaoMethodReturnEntityType(element.returnType);
    String returnStr = SQLiteasyUtil.getElementDesc(element.returnType);

    List<String> imports = List();
    imports.addAll(codeFragment.importList ?? []);
    imports.add(Const.packageAnnotation);
    if (retEntityType != null) {
      imports.add(retEntityType.element.location.components[0]);
    }
    return CodeFragment("""
    @override
    ${returnStr} ${element.name}(${parameterList}) async {
      ${_genDaoMethodBody(entityInfo, element, statement)}
    }
    """, imports);
  }

  String _genDaoMethodBody(EntityInfo entityInfo, MethodElement element, SqlStatement statement) {
    switch (statement.methodType) {
      case DaoMethodType.Insert:
        return _genCommonDaoMethodBody(entityInfo, element, statement);
      case DaoMethodType.Delete:
        return _genCommonDaoMethodBody(entityInfo, element, statement);
      case DaoMethodType.Update:
        return _genUpdateDaoMethodBody(entityInfo, element, statement);
        break;
      case DaoMethodType.Query:
        return _genCommonDaoMethodBody(entityInfo, element, statement);
        break;
    }
  }

  String _genCommonDaoMethodBody(EntityInfo entityInfo, MethodElement element, SqlStatement statement) {
    ParameterElement param = element.parameters.isNotEmpty ? element.parameters[0] : null;
    String paramName = param?.name;
    String execBody;
    List<String> fieldBindStatements = List();
    final genFieldBindStatements = (String entityName) {
      for (int i = 1; i <= statement.parameterCount; i++) {
        if (statement.paramIsEntity) {
          fieldBindStatements.add("${entityName}.${statement.getFieldNameAt(i)}");
        } else {
          fieldBindStatements.add("${statement.getFieldNameAt(i)}");
        }
      }
    };

    final paramIsIterable = param?.type?.isDartCoreIterable == true || param?.type?.isDartCoreList == true || param?.type?.isDartCoreSet == true;
    if (paramIsIterable) {
      genFieldBindStatements("e");
      final execStatement =
          """await ${Const.dbFieldName}.exec("${statement.statement}", [${fieldBindStatements.join(",")}])""";
      execBody = """
      for(var e in $paramName) {
        ${_wrapReturnValueStatement(execStatement, entityInfo, element, statement)};
      }
      """;
    } else {
      genFieldBindStatements(paramName);
      final execStatement =
          """await ${Const.dbFieldName}.exec("${statement.statement}", [${fieldBindStatements.join(",")}])""";
      execBody = """
      ${_wrapReturnValueStatement(execStatement, entityInfo, element, statement)};
      """;
    }
    return """
    ${_genReturnValueDefineStatement(entityInfo, element, statement)}
    await ${Const.dbFieldName}.runInTransaction(() async { 
      ${execBody}
    });
    ${_genReturnStatement(entityInfo, element, statement)}
    """;
  }

  String _genQueryDaoMethodBody(EntityInfo entityInfo, MethodElement element, SqlStatement statement) {

  }

  String _genUpdateDaoMethodBody(EntityInfo entityInfo, MethodElement element, SqlStatement statement) {
    ParameterElement param = element.parameters[0];
    String paramName = param.name;
    String execBody;

    final nullValueStrategy = SQLiteasyUtil.getNullValueStrategyStr(statement.nullValueStrategy);
    final isBatch = param.type.isDartCoreIterable || param.type.isDartCoreList || param.type.isDartCoreSet;
    List<String> columns = List();
    List<String> primaryKeys = List();
    final varName = isBatch ? "e" : paramName;
    entityInfo.columns.forEach((element) {
      if (element.isPrimaryKey == true) {
        primaryKeys.add("\"${element.name}\": ${varName}.${element.field.name}");
      } else {
        columns.add("\"${element.name}\": ${varName}.${element.field.name}");
      }
    });
    if (isBatch) {
      final execStatement =
          """await ${Const.dbFieldName}.update("${entityInfo.tableName}", {${columns.join(",")}}, {${primaryKeys.join(",")}}, ${nullValueStrategy});""";
      execBody = """
      for(var e in $paramName) {
        ${_wrapReturnValueStatement(execStatement, entityInfo, element, statement)}
      }
      """;
    } else {
      final execStatement =
          """await ${Const.dbFieldName}.update("${entityInfo.tableName}", {${columns.join(",")}}, {${primaryKeys.join(",")}}, ${nullValueStrategy})""";
      execBody = """
      ${_wrapReturnValueStatement(execStatement, entityInfo, element, statement)};
      """;
    }
    return """
    ${_genReturnValueDefineStatement(entityInfo, element, statement)}
    await ${Const.dbFieldName}.runInTransaction(() async { 
      ${execBody}
    });
    ${_genReturnStatement(entityInfo, element, statement)}
    """;
  }

  String _genReturnValueDefineStatement(EntityInfo entityInfo, MethodElement element, SqlStatement sqlStatement) {
    final entityType = DatabaseUtil.getDaoMethodReturnEntityType(element.returnType);
    if (entityType == null || entityType.isVoid) {
      return "";
    }

    if (sqlStatement.methodType != DaoMethodType.Query) {
      // 非select方法，只接受int类型返回值，返回影响的数据行数
      if (!entityType.isDartCoreInt && !entityType.isDynamic) {
        throw SQLiteasyBuildError("${sqlStatement.methodType}类型的DaoMethod返回值只能为Future<int>或Future");
      }
      return "int __ret = 0;";
    } else {
      final retType = (element.returnType as ParameterizedType).typeArguments[0];
      bool isDartCollection = DatabaseUtil.isCollectionType(retType);
      if (retType.isDartCoreList) {
        return "final __ret${isDartCollection ? " = <${DatabaseUtil.getTypeName(entityType)}>[]" : ""};";
      } else if (isDartCollection) {
        return "final __ret${isDartCollection ? " = ${retType.element.displayName}<${DatabaseUtil.getTypeName(entityType)}>()" : ""};";
      } else {
        return "${DatabaseUtil.getTypeName(entityType)} __ret;";
      }
    }
  }

  String _genReturnStatement(EntityInfo entityInfo, MethodElement element, SqlStatement sqlStatement) {
    final retType = DatabaseUtil.getDaoMethodReturnEntityType(element.returnType);
    if (retType == null || retType.isVoid) {
      return "";
    }
    return "return __ret;";
  }

  String _wrapReturnValueStatement(String statement, EntityInfo entityInfo,
      MethodElement element, SqlStatement sqlStatement) {
    final entityType = DatabaseUtil.getDaoMethodReturnEntityType(element.returnType);
    if (entityType == null || entityType.isVoid) {
      return statement;
    }

    if (sqlStatement.methodType != DaoMethodType.Query) {
      // 非select方法，只接受int类型返回值，返回影响的数据行数
      if (!entityType.isDartCoreInt && !entityType.isDynamic) {
        throw SQLiteasyBuildError("${sqlStatement.methodType}类型的DaoMethod返回值只能为int或dynamic, 而不是${entityType}");
      }
      return "__ret += ${statement} as int";
    }

    DartType retType = DatabaseUtil.getDaoMethodReturnType(element.returnType);
    bool isDartCollection = DatabaseUtil.isCollectionType(retType);
    _valueConverterEntitySet.add(entityType);
    String statementWrapped = "const ${_getValueConverterName(entityType)}().convert(${statement})";
    if (isDartCollection) {
      return "__ret.addAll(${statementWrapped})";
    } else {
      return """final resultList = ${statementWrapped};
         if (resultList.isEmpty) {
            return null;
         }
         __ret = resultList[0];""";
    }
  }

  bool _isReturnCollection(DartType type) {
    if (type == null) {
      return false;
    }
    if (type.isDartAsyncFuture) {
      final parameterized = type as ParameterizedType;
      if (parameterized.typeArguments.isEmpty) {
        return false;
      }
      type = parameterized.typeArguments[0];
    }
    return type.isDartCoreSet || type.isDartCoreList;
  }

  /// 从Dao中生成sql语句
  List _generateSqlStatement(MethodElement element) {
    DartObject isDaoAction = _daoActionChecker.firstAnnotationOf(element);
    if (isDaoAction == null && element.isAbstract) {
      throw SQLiteasyBuildError("the abstract method of dao must be annotated with Dao Annotation like Insert, Update, Delete or Select.", element.location.components[0]);
    } else if (isDaoAction != null && !element.isAbstract) {
      throw SQLiteasyBuildError("the dao method must be abstract.");
    }

    DartObject queryObject = _queryChecker.firstAnnotationOf(element);
    if (queryObject != null) {
      return _handleQuery(element, queryObject.toQuery());
    }

    DartObject insertObject = _insertChecker.firstAnnotationOf(element);
    if (insertObject != null) {
      return _handleInsert(element, insertObject.toInsert());
    }

    DartObject updateObject = _updateChecker.firstAnnotationOf(element);
    if (updateObject != null) {
      return _handleUpdate(element, updateObject.toUpdate());
    }

    DartObject deleteObject = _deleteChecker.firstAnnotationOf(element);
    if (deleteObject != null) {
      return _handleDelete(element, deleteObject.toDelete());
    }

    return null;
  }

  List _handleInsert(MethodElement methodElement, InsertInfo insert) {
    final tag = "handleInsert";
    log(tag, "生成Insert方法: MethodElement=${methodElement}, insertInfo=${insert}");
    var param = methodElement.parameters;
    if (param.length != 1) {
      throw SQLiteasyBuildError("the parameter count of a insert method must be one.");
    }
    var paramElement = param[0];
    DartType entityType = DatabaseUtil.getEntityType(paramElement.type);
    if (entityType == null) {
      throw SQLiteasyBuildError("the entity type (${paramElement.type}) of insert dao method is invalid");
    }

    final entityInfo = DatabaseUtil.getEntityInfoFromElement(entityType.element);
    if (entityInfo == null) {
      throw SQLiteasyBuildError("The parameter of insert method must be a Entity or a list of Entity");
    }
    log(tag, "entityInfo=${entityInfo}");
    final columnCount = entityInfo.columns.length;
    final conflictAction = conflictStrategyMap[insert.conflictStrategy];

    log(tag, "conflictAction=${conflictAction}");

    final statementBuilder = StringBuffer();
    if (conflictAction == null) {
      statementBuilder.write("INSERT ");
    } else {
      statementBuilder.write("INSERT OR $conflictAction ");
    }

    statementBuilder
        .write("INTO `${entityInfo.tableName}` (${entityInfo.columns.map((e) => "`${e.name}`").join(",")}) VALUES (");
    for (int i = 0; i < columnCount; i++) {
      statementBuilder.write("?");
      if (i != columnCount - 1) {
        statementBuilder.write(",");
      }
    }
    statementBuilder.write(")");

    final sqlStatement = SqlStatement(statementBuilder.toString(), DaoMethodType.Insert);
    for (int i = 0; i < entityInfo.columns.length; i++) {
      ColumnInfo columnInfo = entityInfo.columns[i];
      sqlStatement.bindValueField(i + 1, columnInfo.field.name);
      sqlStatement.bindColumnName(i + 1, columnInfo.name);
    }
    CodeFragment codeFragment = EntityInsertionAdapterGen.gen(entityInfo, sqlStatement.statement);
    return [sqlStatement, codeFragment, entityInfo];
  }

  List _handleQuery(MethodElement methodElement, QueryInfo query) {
    Set<String> paramSet = Set();
    methodElement.parameters.forEach((element) {
      paramSet.add(element.name);
    });
    List<String> paramList = List();
    final pattern = RegExp(r":([a-zA-Z0-9$_]*)");
    final statement = query.statement.replaceAllMapped(pattern, (Match match) {
      final paramName = match.group(1);
      if (!paramSet.contains(paramName)) {
        throw SQLiteasyBuildError("该dao方法参数列表中不存在${paramName}");
      }
      paramList.add(match.group(1));
      return "?";
    });
    final sqlStatement = SqlStatement(statement, DaoMethodType.Query, false);
    int index = 1;
    paramList.forEach((element) {
      sqlStatement.bindValueField(index++, element);
    });
    final codeFragment = CodeFragment("");
    return [sqlStatement, codeFragment, null];
  }

  List _handleUpdate(MethodElement methodElement, UpdateInfo update) {
    EntityInfo entityInfo = _getUpdateDaoMethodTargetEntity(methodElement, update);
    NullValueStrategy nullValueStrategy =
        update.nullValueStrategy ?? _databaseInfo.nullValueStrategy ?? NullValueStrategy.apply;
    List<String> primaryKeyList = entityInfo.primaryKeys.map((e) => e.name).toList();
    List<String> whereList = List();
    List<String> columnList = List();
    List<FieldElement> whereFieldList = List();
    List<FieldElement> columnFieldList = List();
    entityInfo.columns.forEach((element) {
      if (!primaryKeyList.contains(element.name)) {
        columnList.add("`${element.name}`=?");
        columnFieldList.add(element.field);
      } else {
        whereList.add("`${element.name}`=?");
        whereFieldList.add(element.field);
      }
    });
    final sql = "UPDATE `${entityInfo.tableName}` SET ${columnList.join(",")} WHERE ${whereList.join(" and ")}";
    final statement = SqlStatement(sql, DaoMethodType.Update);
    int index = 1;
    columnFieldList.forEach((element) {
      statement.bindValueField(index++, element.name);
    });
    whereFieldList.forEach((element) {
      statement.bindValueField(index++, element.name);
    });
    statement.nullValueStrategy = nullValueStrategy;
    return [
      statement,
      CodeFragment("", [
        entityInfo.location,
      ]),
      entityInfo
    ];
  }

  List _handleDelete(MethodElement methodElement, DeleteInfo delete) {
    final array = _getDeleteDaoMethodTargetEntity(methodElement, delete);
    EntityInfo entityInfo = array[0];
    final paramIsEntity = array[1];
    final statement =
        "DELETE FROM `${entityInfo.tableName}` WHERE ${entityInfo.primaryKeys.map((e) => "`${e.name}`=?").join(",")}";
    SqlStatement sqlStatement = SqlStatement(statement, DaoMethodType.Delete, paramIsEntity);
    int index = 1;
    entityInfo.primaryKeys.forEach((element) {
      sqlStatement.bindColumnName(index, element.name);
      if (paramIsEntity) {
        sqlStatement.bindValueField(index, element.field.name);
      } else {
        if (element.type != DatabaseUtil.getSQLiteType(methodElement.parameters[index - 1].type)) {
          throw SQLiteasyBuildError("Delete方法参数类型与主键类型不一致");
        }
        sqlStatement.bindValueField(index, methodElement.parameters[index - 1].name);
      }
      index++;
    });
    return [
      sqlStatement,
      CodeFragment("", [entityInfo.location, Const.packageAnnotation]),
      entityInfo
    ];
  }

  List _getDeleteDaoMethodTargetEntity(MethodElement methodElement, DeleteInfo delete) {
    bool paramIsEntity = delete.entity == null;
    // 有两种情况，一是传入实体类，而是传入基本数据类型，在Delete注解中指定目标实体类
    DartType entityType;
    if (!paramIsEntity) {
      // 未指定目标实体类，说明方法参数就是实体类
      entityType = delete.entity;
    } else {
      // 指定了实体类，参数应该是这个实体类的主键
      if (methodElement.parameters.length != 1) {
        throw SQLiteasyBuildError("the parameter count of a delete method must be one.");
      }
      final param = methodElement.parameters[0];
      entityType = DatabaseUtil.getEntityType(param.type);
    }
    return [DatabaseUtil.getEntityInfoFromElement(entityType?.element), paramIsEntity];
  }

  EntityInfo _getUpdateDaoMethodTargetEntity(MethodElement methodElement, UpdateInfo updateInfo) {
    if (methodElement.parameters.length != 1) {
      throw SQLiteasyBuildError("the parameter count of a update method must be one.");
    }
    final param = methodElement.parameters[0];
    DartType entityType = DatabaseUtil.getEntityType(param.type);
    return DatabaseUtil.getEntityInfoFromElement(entityType?.element);
  }

  CodeFragment _genValueConverterOfEntity(DartType type) {

  }

  String _getValueConverterName(DartType type) {
    if (_valueConverterNameMap.containsKey(type)) {
      return _valueConverterNameMap[type];
    }

    if (type.isDartAsyncFuture) {
      if ((type as ParameterizedType).typeArguments.length != 1) {
        throw SQLiteasyBuildError("");
      }
      type = (type as ParameterizedType).typeArguments[0];
    }
    if (type.isDartCoreList &&
        type is ParameterizedType &&
        type.typeArguments.length == 1 &&
        type.typeArguments[0].isDartCoreInt) {
      return "${BytesValueConverter}";
    }

    return "_${type.element.displayName}ValueConverter";
  }

  Set<DartType> get valueConverterEntitySet {
    return _valueConverterEntitySet;
  }
}
